package robocup.component.math;

/**
 * This class is used to encode and decode floating-point values into characters
 * and vice versa. Therefore it provides generic methods. Additionally, it
 * defines the set of alloed characters, which are used for encoding and can be
 * sent to the server within say-messages.
 */
public class NumberConverter {

  /**
   * This array holds all allowed characters, which can be used to encode
   * say-messages. Up to now, these are 73 different characters. Note that the
   * order of them corresponds to that of their integer-values, which should not
   * be changed.
   */
  public static final char[] digits = { '(', ')', '*', '+', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', '>', '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; // 73
                                                                                                                                                                                                                                                                                                                                                                                                                        // characters
                                                                                                                                                                                                                                                                                                                                                                                                                        // (without
                                                                                                                                                                                                                                                                                                                                                                                                                        // space)

  // private static char space = ' ';

  /**
   * Lookup the index of a charcter within the digits array.
   * 
   * @param c -
   *          the corresponding character.
   * 
   * @return the characters index in the "digits"-array
   */
  public static int getIndexOf(final char c) {

    for (int i = 0; i < NumberConverter.digits.length; i++) {
      if (c == NumberConverter.digits[i]) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Encodes an array of doubles to an array of characters. This is a flexible
   * approach which adapts the accuracy to the used capacity. Each double value
   * has to be given as a percentage of the max value and must therefore be in
   * the range 0..1. i.e if you wan't to decode a ball speed of 1.35 with max
   * ball speed 2.7 use the value 0.5!
   * 
   * 
   * @param doubles -
   *          array of doubles. each double stands for a values percentage of
   *          the maximal value of its kind that can be code
   * @param numberChars -
   *          capacity of the resulting char-array (proportional to accuracy)
   *          Attention: this value shouldn't be higher than 5 as higher values
   *          lead to wrong results because of the limits for Integers.
   * @return - array of chars which encode the double-values
   */
  public static char[] convertToChars(final double[] doubles,
      final int numberChars) {

    for (int i = 0; i < doubles.length; i++) {

      // doubles represent percentages -> only 0..1
      if (doubles[i] < 0 || doubles[i] > 1) {
        throw new RuntimeException(
            " got wrong percentage:" + doubles[i] + " . should be between 0..1.");
      }

      // as (1.0 * n) % n == (0 * n) % n, we have to get rid of either
      // "0.0" or
      // "1.0"
      if (doubles[i] == 1.0) {
        doubles[i] = 0.9999;
      }
    }

    // if the length of the doubles and chars would be equal the base of the
    // encoding
    // would always be the number of all poss. characters
    // as this is not always the case we have to find a different base
    int base = (int) Math.pow(NumberConverter.digits.length,
        (double) numberChars / doubles.length);

    // the number which results from the doubles if seen as one number
    // (doubles
    // -> number)
    int n = 0;

    for (int i = 0; i < doubles.length; i++) {
      n += ((int) (doubles[i] * base)) * Math.pow(base, i);
      // System.out.println(n + " " + base);
    }

    // the result
    final char[] chars = new char[numberChars];

    // we want to encode with the given characters
    base = NumberConverter.digits.length;

    // encode the number with characters (number -> char)
    for (int i = 0; i < chars.length; i++) {
      chars[i] = NumberConverter.digits[n % base];
      n /= base;
      // System.out.println(chars[i] + " " + n);
    }

    return chars;
  }

  /**
   * Decodes an array of chars to an array of doubles. It has to be known how
   * many doubles are contained. (@see convertToChars)
   * 
   * @param chars -
   *          the code to be decoded. Attention: this value shouldn't be higher
   *          than 5 as higher values lead to wrong results because of the
   *          limits for Integers.
   * @param numberDoubles -
   *          the number of contained double values
   * @return an array of doubles
   */
  public static double[] convertToDoubles(final char[] chars,
      final int start,
      final int length,
      final int numberDoubles) {

    // the chars are decoded on base = number of chars that can be used for
    // encoding
    int base = NumberConverter.digits.length;

    // the value of the "word" resulting from the chars
    int n = 0;

    for (int i = 0; i < length; i++) {
      n += NumberConverter.getIndexOf(chars[i + start]) * Math.pow(base, i);
      // System.out.println(n + " " + base);
    }

    // result
    final double[] doubles = new double[numberDoubles];

    // this undoes the encoding
    base = (int) Math.pow(NumberConverter.digits.length,
        (double) length / numberDoubles);

    for (int i = 0; i < doubles.length; i++) {
      doubles[i] = (double) (n % base) / (base - 1);
      // System.out.println(n + " - " + base);
      n /= base;
    }

    return doubles;
  }

}
